{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "D:\\Anaconda\\Anaconda3\\lib\\site-packages\\ipykernel_launcher.py:11: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version\n",
      "of pandas will change to not sort by default.\n",
      "\n",
      "To accept the future behavior, pass 'sort=False'.\n",
      "\n",
      "To retain the current behavior and silence the warning, pass 'sort=True'.\n",
      "\n",
      "  # This is added back by InteractiveShellApp.init_path()\n"
     ]
    }
   ],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "from sklearn.model_selection import KFold,GridSearchCV,train_test_split\n",
    "from sklearn.metrics import mean_absolute_error\n",
    "from xgboost import XGBRegressor\n",
    "\n",
    "train = pd.read_csv('../data/used_car_train_20200313.csv', sep=' ')\n",
    "test = pd.read_csv('../data/used_car_testB_20200421.csv', sep=' ')\n",
    "\n",
    "# 合并训练数据和测试数据集\n",
    "all_data = pd.concat([train, test], ignore_index=True)\n",
    "\n",
    "# 对 price 做对数变换\n",
    "all_data['price'] = np.log1p(all_data['price'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 处理异常值，如功率大于 600 的值\n",
    "all_data['power'] = all_data['power'].apply(lambda x: 600 if x > 600 else x)\n",
    "\n",
    "# 处理日期相关信息\n",
    "all_data['reg_year'] = all_data['regDate'].apply(lambda x: int(str(x)[:4]))\n",
    "all_data['reg_month'] = all_data['regDate'].apply(lambda x: int(str(x)[4:6]))\n",
    "all_data['reg_day'] = all_data['regDate'].apply(lambda x: int(str(x)[6:]))\n",
    "all_data['creat_year'] = all_data['creatDate'].apply(lambda x: int(str(x)[:4]))\n",
    "all_data['creat_month'] = all_data['creatDate'].apply(lambda x: int(str(x)[4:6]))\n",
    "all_data['creat_day'] = all_data['creatDate'].apply(lambda x: int(str(x)[6:]))\n",
    "\n",
    "#使用时长\n",
    "all_data['used_time'] = (pd.to_datetime(all_data['creatDate'], format='%Y%m%d', errors='coerce') - \n",
    "                            pd.to_datetime(all_data['regDate'], format='%Y%m%d', errors='coerce')).dt.days\n",
    "\n",
    "\n",
    "# 标记汽车没有经过维修\n",
    "all_data['notRepairedDamage'] = all_data['notRepairedDamage'].apply(lambda x: 0 if x == '-' else 1)\n",
    "\n",
    "# 对可分类的连续特征进行分桶，如将功率（power）分成10个分桶，并提取新特征\n",
    "all_data['power_bucket'] = pd.cut(all_data['power'], 10, labels=False)\n",
    "new_cols = ['power_bucket', 'v_0', 'v_4', 'v_8', 'v_12'] \n",
    "for col1 in new_cols:\n",
    "    for col2 in new_cols:\n",
    "        if col1 != col2:\n",
    "            all_data['{}_{}_add'.format(col1, col2)] = all_data[col1] + all_data[col2]\n",
    "            all_data['{}_{}_sub'.format(col1, col2)] = all_data[col1] - all_data[col2]\n",
    "\n",
    "# 处理缺失值\n",
    "all_data['fuelType'] = all_data['fuelType'].fillna(0)\n",
    "all_data['gearbox'] = all_data['gearbox'].fillna(0)\n",
    "all_data['bodyType'] = all_data['bodyType'].fillna(0)\n",
    "all_data['model'] = all_data['model'].fillna(0)\n",
    "\n",
    "# 分离特征和标签\n",
    "train_data = all_data[~all_data['price'].isnull()]\n",
    "test_data = all_data[all_data['price'].isnull()]\n",
    "X_train = train_data.drop(['SaleID', 'name', 'regDate', 'creatDate','seller','offerType','power', 'price','v_2','v_1','regionCode'], axis=1)\n",
    "X_test = test_data.drop(['SaleID', 'name', 'regDate', 'creatDate','seller','offerType','power','price','v_2','v_1','regionCode'], axis=1)\n",
    "y_train = train_data['price']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1\n",
      "2\n",
      "3\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "D:\\Anaconda\\Anaconda3\\lib\\site-packages\\xgboost\\sklearn.py:797: UserWarning: `early_stopping_rounds` in `fit` method is deprecated for better compatibility with scikit-learn, use `early_stopping_rounds` in constructor or`set_params` instead.\n",
      "  UserWarning,\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0]\tvalidation_0-mae:7.31606\n",
      "[200]\tvalidation_0-mae:0.13752\n",
      "[400]\tvalidation_0-mae:0.12830\n",
      "[600]\tvalidation_0-mae:0.12590\n",
      "[800]\tvalidation_0-mae:0.12453\n",
      "[999]\tvalidation_0-mae:0.12358\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "D:\\Anaconda\\Anaconda3\\lib\\site-packages\\xgboost\\sklearn.py:797: UserWarning: `early_stopping_rounds` in `fit` method is deprecated for better compatibility with scikit-learn, use `early_stopping_rounds` in constructor or`set_params` instead.\n",
      "  UserWarning,\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0]\tvalidation_0-mae:7.31349\n",
      "[200]\tvalidation_0-mae:0.13953\n",
      "[400]\tvalidation_0-mae:0.13068\n",
      "[600]\tvalidation_0-mae:0.12807\n",
      "[800]\tvalidation_0-mae:0.12670\n",
      "[999]\tvalidation_0-mae:0.12565\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "D:\\Anaconda\\Anaconda3\\lib\\site-packages\\xgboost\\sklearn.py:797: UserWarning: `early_stopping_rounds` in `fit` method is deprecated for better compatibility with scikit-learn, use `early_stopping_rounds` in constructor or`set_params` instead.\n",
      "  UserWarning,\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0]\tvalidation_0-mae:7.28783\n",
      "[200]\tvalidation_0-mae:0.13918\n",
      "[400]\tvalidation_0-mae:0.13009\n",
      "[600]\tvalidation_0-mae:0.12781\n",
      "[800]\tvalidation_0-mae:0.12660\n",
      "[999]\tvalidation_0-mae:0.12554\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "D:\\Anaconda\\Anaconda3\\lib\\site-packages\\xgboost\\sklearn.py:797: UserWarning: `early_stopping_rounds` in `fit` method is deprecated for better compatibility with scikit-learn, use `early_stopping_rounds` in constructor or`set_params` instead.\n",
      "  UserWarning,\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0]\tvalidation_0-mae:7.30982\n",
      "[200]\tvalidation_0-mae:0.13807\n",
      "[400]\tvalidation_0-mae:0.12864\n",
      "[600]\tvalidation_0-mae:0.12603\n",
      "[800]\tvalidation_0-mae:0.12458\n",
      "[999]\tvalidation_0-mae:0.12349\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "D:\\Anaconda\\Anaconda3\\lib\\site-packages\\xgboost\\sklearn.py:797: UserWarning: `early_stopping_rounds` in `fit` method is deprecated for better compatibility with scikit-learn, use `early_stopping_rounds` in constructor or`set_params` instead.\n",
      "  UserWarning,\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0]\tvalidation_0-mae:7.31933\n",
      "[200]\tvalidation_0-mae:0.13752\n",
      "[400]\tvalidation_0-mae:0.12826\n",
      "[600]\tvalidation_0-mae:0.12585\n",
      "[800]\tvalidation_0-mae:0.12454\n",
      "[999]\tvalidation_0-mae:0.12356\n",
      "五折交叉验证平均 MAE: 542.242269271768\n",
      "4\n",
      "最优模型参数： {'objective': 'reg:squarederror', 'base_score': 0.5, 'booster': 'gbtree', 'callbacks': None, 'colsample_bylevel': 1, 'colsample_bynode': 1, 'colsample_bytree': 0.6, 'early_stopping_rounds': None, 'enable_categorical': False, 'eval_metric': 'mae', 'gamma': 0.005, 'gpu_id': -1, 'grow_policy': 'depthwise', 'importance_type': None, 'interaction_constraints': '', 'learning_rate': 0.03, 'max_bin': 256, 'max_cat_to_onehot': 4, 'max_delta_step': 0, 'max_depth': 10, 'max_leaves': 0, 'min_child_weight': 1, 'missing': nan, 'monotone_constraints': '()', 'n_estimators': 1000, 'n_jobs': -1, 'num_parallel_tree': 1, 'predictor': 'auto', 'random_state': 17, 'reg_alpha': 0, 'reg_lambda': 1.0, 'sampling_method': 'uniform', 'scale_pos_weight': 1, 'subsample': 0.7, 'tree_method': 'exact', 'validate_parameters': 1, 'verbosity': None}\n",
      "5\n"
     ]
    }
   ],
   "source": [
    "#网格\n",
    "param_grid = {\n",
    "    'max_depth':[10,13],\n",
    "    'learning_rate': [0.03, 0.05]\n",
    "}\n",
    "\n",
    "print(1)\n",
    "# 创建XGBRegressor模型实例（初始参数随意给定，后续通过网格搜索确定最优参数）\n",
    "xgb_model = XGBRegressor(\n",
    "    n_estimators=1000,\n",
    "    objective='reg:squarederror',\n",
    "    n_jobs=-1,\n",
    "    subsample=0.7,\n",
    "    gamma=0.005,\n",
    "    colsample_bytree=0.6,\n",
    "    random_state=17,\n",
    "    eval_metric='mae',\n",
    "    reg_lambda=1.0\n",
    ")\n",
    "print(2)\n",
    "\n",
    "#抽取百分之20的数据做网格搜索\n",
    "search_size=int(0.2*len(X_train))\n",
    "x_search,_,y_search,_=train_test_split(X_train,y_train,train_size=search_size,random_state=17)\n",
    "grid_search = GridSearchCV(xgb_model, param_grid, cv=3, scoring='neg_mean_absolute_error')\n",
    "grid_search.fit(x_search, y_search)\n",
    "best_model = grid_search.best_estimator_\n",
    "\n",
    "\n",
    "print(3)\n",
    "# 使用得到的最优模型进行五折交叉验证，评估模型泛化能力\n",
    "skf = KFold(n_splits=5, shuffle=True, random_state=17)\n",
    "validation_scores = []\n",
    "test_pre= np.zeros(len(X_test))\n",
    "for train_index, val_index in skf.split(X_train, y_train):\n",
    "    tr_x, tr_y = X_train.iloc[train_index], y_train.iloc[train_index]\n",
    "    vl_x, vl_y = X_train.iloc[val_index], y_train.iloc[val_index]\n",
    "    best_model.fit(\n",
    "        tr_x, tr_y,\n",
    "        eval_set=[(vl_x, vl_y)],\n",
    "        early_stopping_rounds=100,\n",
    "        verbose=200\n",
    "    )\n",
    "    val_pred = best_model.predict(vl_x)\n",
    "    mae = mean_absolute_error(np.expm1(vl_y), np.expm1(val_pred))\n",
    "    validation_scores.append(mae)\n",
    "    test_predict +=best_model.predict(X_test) /skf.n_splits\n",
    "\n",
    "print(\"五折交叉验证平均 MAE:\", np.mean(validation_scores))\n",
    "\n",
    "# 第三步：使用最优模型对测试集进行预测\n",
    "# test_predict = best_model.predict(X_test)\n",
    "print(4)\n",
    "# 计算并输出最终的MAE值\n",
    "# mae = mean_absolute_error(np.expm1(y_train), np.expm1(best_model.predict(X_train)))\n",
    "# print(\"最终MAE: {:.3f}\".format(mae))\n",
    "# 输出最优模型的参数\n",
    "print(\"最优模型参数：\", best_model.get_params())\n",
    "print(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 保存预测结果，并输出提示信息\n",
    "submission = pd.DataFrame({'SaleID': test_data['SaleID'], 'price': np.expm1(test_predict)})\n",
    "submission.to_csv('xgb_submission.csv', index=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Training on Fold 1\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "D:\\Anaconda\\Anaconda3\\lib\\site-packages\\xgboost\\sklearn.py:797: UserWarning: `early_stopping_rounds` in `fit` method is deprecated for better compatibility with scikit-learn, use `early_stopping_rounds` in constructor or`set_params` instead.\n",
      "  UserWarning,\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0]\tvalidation_0-mae:7.16193\n",
      "[200]\tvalidation_0-mae:0.12742\n",
      "[400]\tvalidation_0-mae:0.12455\n",
      "[600]\tvalidation_0-mae:0.12313\n",
      "[800]\tvalidation_0-mae:0.12245\n",
      "[999]\tvalidation_0-mae:0.12174\n",
      "Training on Fold 2\n",
      "[0]\tvalidation_0-mae:7.16879\n",
      "[200]\tvalidation_0-mae:0.12771\n",
      "[400]\tvalidation_0-mae:0.12527\n",
      "[600]\tvalidation_0-mae:0.12364\n",
      "[800]\tvalidation_0-mae:0.12276\n",
      "[999]\tvalidation_0-mae:0.12201\n",
      "Training on Fold 3\n",
      "[0]\tvalidation_0-mae:7.15379\n",
      "[200]\tvalidation_0-mae:0.12946\n",
      "[400]\tvalidation_0-mae:0.12681\n",
      "[600]\tvalidation_0-mae:0.12570\n",
      "[800]\tvalidation_0-mae:0.12480\n",
      "[999]\tvalidation_0-mae:0.12415\n",
      "Training on Fold 4\n",
      "[0]\tvalidation_0-mae:7.17167\n",
      "[200]\tvalidation_0-mae:0.13048\n",
      "[400]\tvalidation_0-mae:0.12743\n",
      "[600]\tvalidation_0-mae:0.12616\n",
      "[800]\tvalidation_0-mae:0.12544\n",
      "[999]\tvalidation_0-mae:0.12481\n",
      "Training on Fold 5\n",
      "[0]\tvalidation_0-mae:7.14436\n",
      "[200]\tvalidation_0-mae:0.13138\n",
      "[400]\tvalidation_0-mae:0.12826\n",
      "[600]\tvalidation_0-mae:0.12675\n",
      "[800]\tvalidation_0-mae:0.12586\n",
      "[999]\tvalidation_0-mae:0.12518\n",
      "Training on Fold 6\n",
      "[0]\tvalidation_0-mae:7.13098\n",
      "[200]\tvalidation_0-mae:0.12762\n",
      "[400]\tvalidation_0-mae:0.12484\n",
      "[600]\tvalidation_0-mae:0.12344\n",
      "[800]\tvalidation_0-mae:0.12252\n",
      "[999]\tvalidation_0-mae:0.12176\n",
      "Training on Fold 7\n",
      "[0]\tvalidation_0-mae:7.15426\n",
      "[200]\tvalidation_0-mae:0.12963\n",
      "[400]\tvalidation_0-mae:0.12608\n",
      "[600]\tvalidation_0-mae:0.12464\n",
      "[800]\tvalidation_0-mae:0.12374\n",
      "[999]\tvalidation_0-mae:0.12313\n",
      "Training on Fold 8\n",
      "[0]\tvalidation_0-mae:7.16415\n",
      "[200]\tvalidation_0-mae:0.12630\n",
      "[400]\tvalidation_0-mae:0.12358\n",
      "[600]\tvalidation_0-mae:0.12192\n",
      "[800]\tvalidation_0-mae:0.12101\n",
      "[999]\tvalidation_0-mae:0.12014\n",
      "Training on Fold 9\n",
      "[0]\tvalidation_0-mae:7.16956\n",
      "[200]\tvalidation_0-mae:0.12814\n",
      "[400]\tvalidation_0-mae:0.12511\n",
      "[600]\tvalidation_0-mae:0.12369\n",
      "[800]\tvalidation_0-mae:0.12288\n",
      "[999]\tvalidation_0-mae:0.12236\n",
      "Training on Fold 10\n",
      "[0]\tvalidation_0-mae:7.16747\n",
      "[200]\tvalidation_0-mae:0.12779\n",
      "[400]\tvalidation_0-mae:0.12443\n",
      "[600]\tvalidation_0-mae:0.12308\n",
      "[800]\tvalidation_0-mae:0.12229\n",
      "[999]\tvalidation_0-mae:0.12152\n",
      "MAE: 526.143\n"
     ]
    }
   ],
   "source": [
    "#手动调参最优结果\n",
    "# 定义模型参数\n",
    "xgb_model = XGBRegressor(\n",
    "    max_depth=10,\n",
    "    learning_rate=0.05,\n",
    "    n_estimators=1000,\n",
    "    gamma=0.005,\n",
    "    subsample=0.8,\n",
    "    colsample_bytree=0.7,\n",
    "    objective='reg:squarederror',\n",
    "    n_jobs=-1,\n",
    "    random_state=17,\n",
    "    eval_metric='mae'\n",
    ")\n",
    "\n",
    "# 交叉验证以及训练模型\n",
    "skf = KFold(n_splits=10, shuffle=True, random_state=17)\n",
    "oof = np.zeros(len(X_train))\n",
    "test_predict = np.zeros(len(X_test))\n",
    "for i, (train_index, val_index) in enumerate(skf.split(X_train, y_train)):\n",
    "    print(\"Training on Fold {}\".format(i+1))\n",
    "    tr_x, tr_y = X_train.iloc[train_index], y_train.iloc[train_index]\n",
    "    vl_x, vl_y = X_train.iloc[val_index], y_train.iloc[val_index]\n",
    "    xgb_model.fit(\n",
    "        tr_x, tr_y,\n",
    "        eval_set=[(vl_x, vl_y)],\n",
    "        early_stopping_rounds=100,\n",
    "        verbose=200\n",
    "    )\n",
    "\n",
    "    oof[val_index] = xgb_model.predict(vl_x)\n",
    "    test_predict += xgb_model.predict(X_test) / skf.n_splits\n",
    "\n",
    "mae = mean_absolute_error(np.expm1(y_train), np.expm1(oof))\n",
    "print(\"MAE: {:.3f}\".format(mae))\n",
    "\n",
    "# 保存预测结果\n",
    "res = pd.DataFrame({'SaleID': test_data['SaleID'], 'price': np.expm1(test_predict)})\n",
    "res.to_csv('xgb.csv', index=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#网格调参\n",
    "param_grid = {\n",
    "    'max_depth':[7,10,13],\n",
    "    'learning_rate': [0.03, 0.05],\n",
    "    'n_estimators': [500,1000]\n",
    "}\n",
    "\n",
    "print(1)\n",
    "# 创建XGBRegressor模型实例（初始参数随意给定，后续通过网格搜索确定最优参数）\n",
    "xgb_model = XGBRegressor(\n",
    "    objective='reg:squarederror',\n",
    "    n_jobs=-1,\n",
    "    subsample=0.7,\n",
    "    gamma=0.005,\n",
    "    colsample_bytree=0.6,\n",
    "    random_state=17,\n",
    "    eval_metric='mae',\n",
    "    reg_lambda=1.0\n",
    ")\n",
    "print(2)\n",
    "\n",
    "#抽取百分之20的数据做网格搜索\n",
    "search_size=int(0.2*len(X_train))\n",
    "x_search,_,y_search,_=train_test_split(X_train,y_train,train_size=search_size,random_state=17)\n",
    "grid_search = GridSearchCV(xgb_model, param_grid, cv=3, scoring='neg_mean_absolute_error')\n",
    "grid_search.fit(x_search, y_search)\n",
    "best_model = grid_search.best_estimator_\n",
    "\n",
    "# 交叉验证以及训练模型\n",
    "skf = KFold(n_splits=5, shuffle=True, random_state=17)\n",
    "oof = np.zeros(len(X_train))\n",
    "test_predict = np.zeros(len(X_test))\n",
    "for i, (train_index, val_index) in enumerate(skf.split(X_train, y_train)):\n",
    "    print(\"Training on Fold {}\".format(i+1))\n",
    "    tr_x, tr_y = X_train.iloc[train_index], y_train.iloc[train_index]\n",
    "    vl_x, vl_y = X_train.iloc[val_index], y_train.iloc[val_index]\n",
    "    best_model.fit(\n",
    "        tr_x, tr_y,\n",
    "        eval_set=[(vl_x, vl_y)],\n",
    "        early_stopping_rounds=100,\n",
    "        verbose=200\n",
    "    )\n",
    "\n",
    "    oof[val_index] = best_model.predict(vl_x)\n",
    "    test_predict += best_model.predict(X_test) / skf.n_splits\n",
    "\n",
    "mae = mean_absolute_error(np.expm1(y_train), np.expm1(oof))\n",
    "print(\"MAE: {:.3f}\".format(mae))\n",
    "\n",
    "# 保存预测结果\n",
    "res = pd.DataFrame({'SaleID': test_data['SaleID'], 'price': np.expm1(test_predict)})\n",
    "res.to_csv('xgb_submission.csv', index=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "最优模型参数： {'objective': 'reg:squarederror', 'base_score': 0.5, 'booster': 'gbtree', 'callbacks': None, 'colsample_bylevel': 1, 'colsample_bynode': 1, 'colsample_bytree': 0.6, 'early_stopping_rounds': None, 'enable_categorical': False, 'eval_metric': 'mae', 'gamma': 0.005, 'gpu_id': -1, 'grow_policy': 'depthwise', 'importance_type': None, 'interaction_constraints': '', 'learning_rate': 0.03, 'max_bin': 256, 'max_cat_to_onehot': 4, 'max_delta_step': 0, 'max_depth': 10, 'max_leaves': 0, 'min_child_weight': 1, 'missing': nan, 'monotone_constraints': '()', 'n_estimators': 1000, 'n_jobs': -1, 'num_parallel_tree': 1, 'predictor': 'auto', 'random_state': 17, 'reg_alpha': 0, 'reg_lambda': 1.0, 'sampling_method': 'uniform', 'scale_pos_weight': 1, 'subsample': 0.7, 'tree_method': 'exact', 'validate_parameters': 1, 'verbosity': None}\n"
     ]
    }
   ],
   "source": [
    "print(\"最优模型参数：\", best_model.get_params())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
